package muspelheim.toolbox.test;

import java.lang.annotation.Annotation;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.junit.runner.Description;
import org.junit.runner.Runner;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.ParentRunner;
import org.junit.runners.model.InitializationError;
import org.reflections.Configuration;
import org.reflections.Reflections;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.scanners.TypeAnnotationsScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.util.FilterBuilder;

/**
 * 
 * @author Sonni Nielsen
 *
 */
public class AnnotationRunner extends ParentRunner<Runner> {
	private AnnotationScanner scanner;
	private List<Runner> runners;
	private Grouping grouping;
	private PackagePrefix packagePrefix;
	
	/**
	 * @throws InitializationError
	 * @throws IllegalStateException if klass is not annotated with Grouping 
	 * @throws IllegalStateException if klass is not annotated with PackagePrefix
	 */
	public AnnotationRunner(Class<?> klass) throws InitializationError {
		super(klass);	
		
		initialiseRunners(klass);
	}

	@Override
	protected Description describeChild(Runner runner) {
		return runner.getDescription();
	}

	@Override
	protected List<Runner> getChildren() {
		return runners;
	}
	
	@Override
	protected void runChild(Runner runner, RunNotifier notifier) {
		runner.run(notifier);
	}
	
//-------------------
// Private methods
//-------------------

	/**
	 * Initialises runners with tests specified by the @see Grouping annotation and 
	 * belongs to the specified package/sub-package specified in @see PackagePrefix
	 * 
	 * @param klass - the class that TODO
	 */
	private void initialiseRunners(Class<?> klass){
		scanner = new AnnotationScanner();
		
		extractMetadata(klass);
				
		Set<Class<?>> foundTests = scanner.getTestsAnnotatedWith(grouping.group(), packagePrefix.prefix());
		
		runners = createRunners(foundTests);
	}
	
	/**
	 * Extracts meta-data from the Grouping and PackagePrefix annotations
	 * @param klass
	 */
	private void extractMetadata(Class<?> klass) {
		grouping = klass.getAnnotation(Grouping.class);
		packagePrefix = klass.getAnnotation(PackagePrefix.class);
		
		if(grouping == null)
			throw new IllegalStateException("Test class is not annotated with Grouping");		
		
		if(packagePrefix == null)
			throw new IllegalStateException("Test class is not annotated with PackagePrefix");		
	}

	/**
	 * Wraps the found tests in a BlockJUnit4ClassRunner   
	 * 
	 * @param foundTests
	 * @return TODO 
	 */
	private List<Runner> createRunners(Set<Class<?>> foundTests){
		List<Runner> runners = new LinkedList<Runner>();
		
		Iterator<Class<?>> iterator = foundTests.iterator();		
		while(iterator.hasNext()){
			try {
				runners.add(new BlockJUnit4ClassRunner(iterator.next()));
			} 
			catch (InitializationError e) {
				collectInitializationErrors(e.getCauses());		
			}
		}

		return runners;
	}

//-------------------------------
// Inner class AnnotationScanner
//-------------------------------
	
	private class AnnotationScanner {

		public  Set<Class<?>> getTestsAnnotatedWith(Class<? extends Annotation> annotation, String packagePrefix) {
			Reflections reflections =  new Reflections(createConfiguration(packagePrefix));		
							
			return reflections.getTypesAnnotatedWith(annotation);
		}	
		
		private Configuration createConfiguration(String packagePrefix){
			ConfigurationBuilder configuration = new ConfigurationBuilder();
			
			configuration.filterInputsBy(new FilterBuilder().include(packagePrefix))
	        			 .setUrls(ClasspathHelper.forClassLoader())
	        			 .setScanners(new TypeAnnotationsScanner(), new SubTypesScanner());
			
			return configuration;
		}
	}
}
